using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace Cobalt.Content.Pipeline.Quake.Bsp
{
	[ContentProcessor( DisplayName = "Cobalt Internal - Don't use this." )]
	public class Cobalt_Quake_MaterialProcessor : ContentProcessor<ImportedMaterialContent, Graphics.CobaltMaterialContent>
	{
		[DefaultValue( null )]
		public string BasePath { get; set; }

		[DefaultValue( false )]
		public bool GenerateParallaxTechnique { get; set; }

		#region Texture processing parameters
		private OpaqueDataDictionary texParams = new OpaqueDataDictionary();

		[DefaultValue( typeof( Color ), "255, 0, 255, 255" )]
		public virtual Color ColorKeyColor
		{
			get { return texParams.GetValue( "ColorKeyColor", new Color( 0xFF, 0, 0xFF, 0xFF ) ); }
			set { texParams["ColorKeyColor"] = value; }
		}

		[DefaultValue( true )]
		public virtual bool ColorKeyEnabled
		{
			get { return texParams.GetValue( "ColorKeyEnabled", true ); }
			set { texParams["ColorKeyEnabled"] = value; }
		}
		[DefaultValue( true )]
		public virtual bool GenerateMipmaps
		{
			get { return texParams.GetValue( "GenerateMipmaps", true ); }
			set { texParams["GenerateMipmaps"] = value; }
		}

		[DefaultValue( false )]
		public virtual bool ResizeTexturesToPowerOfTwo
		{
			get { return texParams.GetValue( "ResizeToPowerOfTwo", false ); }
			set { texParams["ResizeToPowerOfTwo"] = value; }
		}

		[DefaultValue( TextureProcessorOutputFormat.DxtCompressed )]
		public virtual TextureProcessorOutputFormat TextureFormat
		{
			get { return texParams.GetValue( "TextureFormat", TextureProcessorOutputFormat.DxtCompressed ); }
			set { texParams["TextureFormat"] = value; }
		}

		private void InitTexParams()
		{
			ColorKeyColor = new Color( 0xFF, 0, 0xFF, 0xFF );
			ColorKeyEnabled = true;
			GenerateMipmaps = true;
			ResizeTexturesToPowerOfTwo = false;
			TextureFormat = TextureProcessorOutputFormat.DxtCompressed;
		}
		#endregion

		public Cobalt_Quake_MaterialProcessor()
		{
			InitTexParams();
		}

		private ImportedMaterialContent input;
		private ContentProcessorContext context;

		public override Graphics.CobaltMaterialContent Process( ImportedMaterialContent input, ContentProcessorContext context )
		{
			this.input = input;
			this.context = context;

			Graphics.CobaltMaterialContent ret = new Graphics.CobaltMaterialContent();
			ret.Layers.Add( 0, CompileDepthLayer() );
			ret.Layers.Add( 1, CompileAmbientLayer() );

			this.context = null;
			this.input = null;

			return ret;
		}

		public EffectMaterialContent CompileDepthLayer()
		{
			Shaders.ShaderCompiler compiler = new Shaders.ShaderCompiler();

			EffectContent fxCode = compiler.CompileDepthLayer( input.ParsedShader );
			fxCode.Identity = input.Identity;

			EffectMaterialContent ret = new EffectMaterialContent();
			ret.CompiledEffect = CompileShaderEffect( fxCode );

			return ret;
		}

		public EffectMaterialContent CompileAmbientLayer()
		{
			Shaders.ShaderCompiler compiler = new Shaders.ShaderCompiler();

			ExternalReference<TextureContent> bumpImage = null;

			if( input.ParsedShader.Sky == null && GenerateParallaxTechnique )
			{
				string baseTexObj = GetBaseTextureObject();
				
				ExternalReference<TextureContent> baseImage;
				if( baseTexObj != null && input.Textures.TryGetValue( baseTexObj, out baseImage ) )
				{
					bumpImage = ImportSubsidiaryImage( baseImage.Filename, "_b",
						"BumpMapProcessor", "CreateBumpMapProcessor" );
					compiler.PomTextureObj = baseTexObj;
				}
			}

			EffectContent fxCode = compiler.CompileAmbientLayer( input.ParsedShader );
			fxCode.Identity = input.Identity;

			EffectMaterialContent ret = new EffectMaterialContent();
			ret.Identity = input.Identity;
			ret.CompiledEffect = CompileShaderEffect( fxCode );

			if( input.ParsedShader.Sky != null )
			{
				//thingace is a sky
				ret.Textures["SkyTexture"] = ImportSkyCube( input.Textures["SkyTexture"] );
			}
			else
			{
				foreach( var tex in input.Textures )
				{
					if( string.IsNullOrEmpty( tex.Value.Filename ) ||
						!File.Exists( tex.Value.Filename ) )
						continue;

					ret.Textures[tex.Key] = context.BuildAsset<TextureContent, TextureContent>(
						tex.Value, "TextureProcessor", texParams, null, null );
				}

				if( bumpImage != null )
					ret.Textures["Bump"] = bumpImage;
			}

			return ret;
		}

		private string GetBaseTextureObject()
		{
			var bundleTextures = Shaders.ShaderCompiler.GetBundleTextures( input.ParsedShader );

			Shaders.TextureBundle bestBundle = null;
			foreach( var b in bundleTextures.Keys )
			{
				if( !(b.TexCoordGen is Shaders.TextureTexCoordGen) )
					continue;

				if( b.TexCoordMods.Count != 0 )
					continue;

				if( bestBundle == null )
				{
					bestBundle = b;
					continue;
				}

				var stageBest = input.ParsedShader.Stages.Find( s => s.Bundles.Contains( bestBundle ) );
				var stageB = input.ParsedShader.Stages.Find( s => s.Bundles.Contains( b ) );

				if( (stageBest.AlphaBlend.Enable || stageBest.AlphaTest.Enable) &&
					!(stageB.AlphaBlend.Enable || stageB.AlphaTest.Enable) )
				{
					bestBundle = b;
					continue;
				}
			}

			return bestBundle != null ? bundleTextures[bestBundle] : null;
		}

		private ExternalReference<TextureContent> ImportSubsidiaryImage( string baseImage, string suffix,
			string existingProcessor, string createProcessor )
		{
			string strippedPath = Path.ChangeExtension( baseImage, null );
			string existing = GetImagePath( strippedPath + suffix );

			if( existing != null )
			{
				return context.BuildAsset<TextureContent, TextureContent>(
					new ExternalReference<TextureContent>( existing ), existingProcessor );
			}
			else
			{
				context.Logger.LogMessage( "Image {0}{1} does not exist, creating with {2}.", strippedPath, suffix, createProcessor );
				return context.BuildAsset<TextureContent, TextureContent>(
					new ExternalReference<TextureContent>( baseImage ), createProcessor, null,
					"TextureImporter", GetAssetName( baseImage ) + suffix );
			}
		}

		private string GetAssetName( string srcPath )
		{
			if( !Path.IsPathRooted( srcPath ) )
				throw new ArgumentException();

			string contentRoot = GetContentRoot( srcPath );

			if( contentRoot == null )
				throw new Exception( "Couldn't find content root path." );

			srcPath = srcPath.Substring( contentRoot.Length );
			if( srcPath.StartsWith( "\\" ) || srcPath.StartsWith( "/" ) )
				srcPath = srcPath.Substring( 1 );

			return Path.ChangeExtension( srcPath, null );
		}

		private string GetContentRoot( string path )
		{
			while( path != null )
			{
				string dir = Path.GetFileName( path );

				if( string.Compare( dir, "Content", StringComparison.InvariantCultureIgnoreCase ) == 0 )
					return path;

				path = Path.GetDirectoryName( path );
			}

			return null;
		}

		private ExternalReference<TextureContent> ImportSkyCube( ExternalReference<TextureContent> texRef )
		{
			string cubeName = Path.GetFileNameWithoutExtension( texRef.Filename );

			if( cubeName.EndsWith( "_up" ) )
				cubeName = cubeName.Substring( 0, cubeName.Length - 3 );

			cubeName = string.Format( "textures\\sky\\cubes\\{0}", cubeName );

			if( BasePath != null )
				cubeName = BasePath + "\\" + cubeName;

			return context.BuildAsset<TextureContent, TextureContent>( texRef, null, null,
				"SkyCubeImporter", cubeName );
		}

		private static string[] imageExtensions = { ".tga", ".jpg", ".bmp", ".dds" };
		public string GetImagePath( string path )
		{
			foreach( string ext in imageExtensions )
			{
				string imgPath = Path.ChangeExtension( path, ext );
				if( File.Exists( imgPath ) )
					return imgPath;
			}

			return null;
		}

		private ExternalReference<CompiledEffect> CompileShaderEffect( EffectContent effect )
		{
			var hasher = new HashContext();
			hasher.Begin();
			hasher.Update( effect.EffectCode );
			Hash hash = hasher.End();

			string assetName = "fx\\" + Helpers.Base32Encode( hash.GetHashBytes() );
			if( BasePath != null )
				assetName = BasePath + "\\" + assetName;

			OpaqueDataDictionary procParams = new OpaqueDataDictionary();
			procParams.Add( "EffectCode", effect.EffectCode );

			return context.BuildAsset<EffectContent, CompiledEffect>(
				new ExternalReference<EffectContent>( effect.Identity.SourceFilename ),
				"Cobalt_Quake_ShaderEffectProcessor", procParams, "Quake_Cobalt_DummyContentImporter",
				assetName );
		}
	}

	[ContentImporter( ".boguscrap", DisplayName = "Cobalt Internals - Don't use this." )]
	public class Quake_Cobalt_DummyContentImporter : ContentImporter<ContentItem>
	{
		public override ContentItem Import( string filename, ContentImporterContext context )
		{
			ContentItem ret = new ContentItem();
			ret.Identity = new ContentIdentity( filename );
			return ret;
		}
	}

	[ContentProcessor( DisplayName = "Cobalt Internal - Don't use this." )]
	public class Cobalt_Quake_ShaderEffectProcessor : ContentProcessor<ContentItem, CompiledEffect>
	{
		public string EffectCode { get; set; }

		public override CompiledEffect Process( ContentItem input, ContentProcessorContext context )
		{
			EffectContent effect = new EffectContent();
			
			effect.Name = input.Name;
			effect.Identity = input.Identity;
			effect.EffectCode = EffectCode;

			return context.Convert<EffectContent, CompiledEffect>( effect, "EffectProcessor" );			
		}
	}
}